#include "command.h"

static int kilo_end = 0;

/*defines*/

#define KILO_VERSION "0.0.1"
#define KILO_TAB_STOP 4
#define KILO_QUIT_TIMES 1

#define CTRL_KEY(k) ((k) & 0x1f) //map key k to releative combination with key Ctrl. k -> Ctrl+k

enum editorKey
{
    BACKSPACE = 127,
    ARROW_LEFT = 1000,
    ARROW_RIGHT,
    ARROW_UP,
    ARROW_DOWN,
    DEL_KEY,
    HOME_KEY,
    END_KEY,
    PAGE_UP,
    PAGE_DOWN
};

enum editorHighlight {
    HL_NORMAL = 0,
    HL_STRING,
    HL_NUMBER,
    HL_MATCH
};

#define HL_HIGHLIGHT_NUMBERS (1<<0)
#define HL_HIGHLIGHT_STRINGS (1<<1)

/*data*/

struct editorSyntax {
    char *filetype;
    char **filematch;
    int flags;
};

//erow means editor row
typedef struct erow {
    int size; //length
    int rsize; // contain the size of the contents of render
    char *chars; //store a line of text
    char *render; //contain the actual characters to draw on the screen for that row of text
    unsigned char *hl; //highlighting
} erow;

struct editorConfig { //configution about the editor
    int cx, cy; // keep track of the cursor
    int rx; // rx is an index into the render field
    int screenrows;
    int screencols;
    int rowoff; // row offset, keep track of the row of file the user is currently scrolled to, actually it is just the top line number in visiable window
    int coloff; // col offset, actually it is just the left index int visiable window
    int numrows; // the number of rows in text buffer
    erow *row; // store multiple lines in text buffer
    int dirty; // keep track of wheter the text loaded in our editor differs from what's in the file ---> warn losing unsaved changes when quit
    char *filename; // display the filename in the status bar
    char statusmsg[80];
    time_t statusmsg_time; // contain the timestamp when we set a statusmsg
    struct editorSyntax *syntax; // NULL means there is no filetype for the current file and no syntax highlighting should be done
    struct termios orig_termios; // store the original terminal
};

struct editorConfig E;

/*filetype*/

// filetypes' extension
char *C_HL_extensions[] = {".c", ".h", ".cpp", NULL};

// hldb means highlight database
struct editorSyntax HLDB[] = { 
    {
        "c",
        C_HL_extensions,
        HL_HIGHLIGHT_NUMBERS | HL_HIGHLIGHT_STRINGS
    },
};

// store the length of the HLDB array
#define HLDB_ENTRIES (sizeof(HLDB) / sizeof(HLDB[0]))

/*prototypes*/

void editorSetStatusMessage(const char* fmt, ...);
void editorRefreshScreen();
char *editorPrompt(char *promptm, void (*callback)(char *, int));

/*terminal*/

void die(const char *s) {
    write(STDOUT_FILENO, "\x1b[2J", 4); //write an escape sequence out to the terminal to clear
    write(STDOUT_FILENO, "\x1b[H", 3); // H command---position the cursor
    perror(s);
    // exit(1);
	kilo_end = 1;
}

void disableRawMode() {
    if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &E.orig_termios) == -1)
        die("tcgetattr");
}

void enableRawMode() {
    if (tcgetattr(STDIN_FILENO, &E.orig_termios) == -1)
        die("tcgetattr");

    struct termios raw = E.orig_termios;
    raw.c_iflag &= ~(IXON | ICRNL | BRKINT | INPCK | ISTRIP); //disable ctrl-s and ctrl-q
    raw.c_oflag &= ~(OPOST); //turn off output processing, such as disable \n -> \r\n
    raw.c_cflag |= (CS8);
    raw.c_lflag &= ~(ECHO | ICANON | ISIG | IEXTEN); // icanon disable canonical mode and isig disable ctrl-c and ctrl-z
    raw.c_cc[VMIN] = 0;
    raw.c_cc[VTIME] = 1;

    if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1)
        die("tcsetattr");
}

//editorReadKey() and editorProcessKeypress() is used to refactoring keyboard input
int editorReadKey() { //wait for one keypress and return it
    int nread;
    char c;
    //while loop means it will keep looping until it reads a char successfully
    while ((nread = read(STDIN_FILENO, &c, 1)) != 1) {
        if (nread == -1 && errno != EAGAIN)
            die("read");
    }

    if (c == '\x1b') {
        // read two more bytes into the seq buffer if read an escape sequence
        char seq[3];
        //assume the user just pressed <esc> if reads time out
        if (read(STDIN_FILENO, &seq[0], 1) != 1)
            return '\x1b';
        if (read(STDIN_FILENO, &seq[1], 1) != 1)
            return '\x1b';

        //<esc>+A means arrow key "up", so mapping it to 'w'
        if (seq[0] == '[') {
            if (seq[1] >= '0' && seq[1] <= '9') {
                if (read(STDIN_FILENO, &seq[2], 1) != 1)
                    return '\x1b';
                if (seq[2] == '~') {
                    switch (seq[1]) {
                        case '1' :
                            return HOME_KEY;
                        case '3' :
                            return DEL_KEY;
                        case '4':
                            return END_KEY;
                        case '5':
                            return PAGE_UP;
                        case '6' :
                            return PAGE_DOWN;
                        case '7' :
                            return HOME_KEY;
                        case '8' :
                            return END_KEY;
                    }
                }
            } else {
                switch(seq[1]) {
                    case 'A' :
                        return ARROW_UP;
                    case 'B' :
                        return ARROW_DOWN;
                    case 'C' :
                        return ARROW_RIGHT;
                    case 'D' :
                        return ARROW_LEFT;
                    case 'H' :
                        return HOME_KEY;
                    case 'F' :
                        return END_KEY;
                }
            }
        } else if (seq[0] == '0') {
            switch (seq[1]) {
                case 'H' :
                    return HOME_KEY;
                case 'F' :
                    return END_KEY;
            }
        }
        return '\x1b';
    } else {
        return c;
    }
}

int getCursorPosition(int *rows, int *cols) {
    char buf[32]; //a buffer, storing the infomation read from terminal
    unsigned int i = 0;

    if (write(STDOUT_FILENO, "\x1b[6n", 4) != 4) // ask terminal return the cursor position
        return -1;

    while (i < sizeof(buf) - 1) {
        if (read(STDIN_FILENO, &buf[i], 1) != 1)
            break;
        if (buf[i] == 'R')
            break;
        i++;
    }

    buf[i] = '\0';

    if (buf[0] != '\x1b' || buf[1] != '[')
        return -1;
    if (sscanf(&buf[2], "%d;%d", rows, cols) != 2)
        return -1;

    return 0;
}

int getWindowSize(int *rows, int *cols) {
    struct winsize ws; // come from <sys/ioctl.h>

    if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) {
        // this is backup way if fail
        if (write(STDOUT_FILENO, "\x1b[999C\x1b[999B", 12) != 12) // C command moves the cursor to right, B command moves the cursor to bottom
            return -1;
        return getCursorPosition(rows, cols);
    }
    else 
    {
        // assign directly if successful
        *cols = ws.ws_col;
        *rows = ws.ws_row;
        return 0;
    }
}

/*syntax highlighting*/

int is_separator(int c) {
    return isspace(c) || c == '\0' || strrchr(",.()+-/*=~%<>[];", c) == NULL;
}

void editorUpdateSyntax(erow *row) {
    row->hl = realloc(row->hl, row->rsize);
    memset(row->hl, HL_NORMAL, row->rsize);

    if (E.syntax == NULL) return;

    int prev_seq = 1; // conside the beginning of the line to be a separator
    int in_string = 0; // keep track of whether we are currently inside a string

    int i;
    while (i < row->rsize) {
        char c = row->render[i];
        unsigned char prev_hl = (i > 0) ? row->hl[i - 1] : HL_NORMAL; // set to the highlight type of the previous character

        if (E.syntax->flags & HL_HIGHLIGHT_STRINGS) {
            if (in_string) {
                row->hl[i] = HL_STRING;
                if (c == '\\' && i + 1 < row->rsize) {
                    row->hl[i + 1] = HL_STRING;
                    i += 2;
                    continue;
                }
                if (c == in_string) in_string = 0;
                i++;
                prev_seq = 1;
                continue;
            } else {
                if (c == '"' || c =='\'') {
                    in_string = c;
                    row->hl[i] = HL_STRING;
                    i++;
                    continue;
                }
            }
        }

        if (E.syntax->flags & HL_HIGHLIGHT_NUMBERS) {
            if ((isdigit(c) && (prev_seq || prev_hl == HL_NUMBER)) || (c == '.' && prev_hl == HL_NUMBER)) {
                row->hl[i] = HL_NUMBER;
                i++;
                prev_seq = 0;
                continue;
            }
        }

        prev_seq = is_separator(c);
        i++;
    }
}

/*row operation*/

int editorRowCxToRx(erow *row, int cx) {
    int rx = 0;
    int j;
    for (j = 0; j < cx; j++) {
        if (row->chars[j] == '\t') 
            rx += (KILO_TAB_STOP - 1) - (rx % KILO_TAB_STOP); // rx moves to the next stop position
        rx++;
    }
    return rx;
}

int editorRowRxToCx(erow *row, int rx) {
    int cur_rx = 0;
    int cx;
    for (cx = 0; cx < row->size; cx++) {
        if (row->chars[cx] == '\t') {
            cur_rx += (KILO_TAB_STOP - 1) - (cur_rx % KILO_TAB_STOP);
        }
        cur_rx++;
        
        if (cur_rx > rx) return cx;
    }
    return cx;
}

void editorUpdateRow(erow* row) {
    //render tabs as multiple space characters
    int tabs = 0;
    int j;
    // count the tabs in order to know how much memory to allocate for render
    for (j = 0; j < row->size; j++) {
        if (row->chars[j] == '\t') tabs++;
    }

    free(row->render);
    row->render = malloc(row->size + tabs * (KILO_TAB_STOP - 1) + 1);

    int idx = 0;
    for (j = 0; j < row->size; j++) {
        if (row->chars[j] == '\t') {
            row->render[idx++] = ' ';
            while (idx % KILO_TAB_STOP != 0) row->render[idx++] = ' '; 
        } else {
            row->render[idx++] = row->chars[j];
        }
    }
    row->render[idx] = '\0';
    row->rsize = idx;
    // after updating render, we call editorUpdateSyntax()
    editorUpdateSyntax(row);
}

// different type words have different color
int editorSyntaxToColor(int hl) {
    switch (hl) {
        case HL_STRING: return 35;
        case HL_NUMBER: return 31;
        case HL_MATCH: return 34;
        default: return 37;
    }
}

void editorSelectSyntaxHighlight() {
    E.syntax = NULL;
    if (E.filename == NULL) return;

    char *ext = strrchr(E.filename, '.');

    for (unsigned int j = 0; j < HLDB_ENTRIES; j++) {
        struct editorSyntax *s = &HLDB[j];
        unsigned int i = 0;
        while (s->filematch[i]) {
            int is_ext = (s->filematch[i][0] == '.');
            if ((is_ext && ext && !strcmp(ext, s->filematch[i])) || (!is_ext && strstr(E.filename, s->filematch[i]))) {
                E.syntax = s;

                int filerow;
                for (filerow = 0; filerow < E.numrows; filerow++) {
                    editorUpdateSyntax(&E.row[filerow]);
                }

                return;
            }
            i++;
        }
    }
}

// append a new line
void editorInsertRow(int at, char *s, size_t len) {
    if (at < 0 || at > E.numrows) return;

    E.row = realloc(E.row, sizeof(erow) * (E.numrows + 1));
    memmove(&E.row[at + 1], &E.row[at], sizeof(erow) * (E.numrows - at));

    E.row = realloc(E.row, sizeof(erow) * (E.numrows + 1));

    E.row[at].size = len;
    E.row[at].chars = malloc(len + 1);
    memcpy(E.row[at].chars, s, len);
    E.row[at].chars[len] = '\0';

    E.row[at].rsize = 0;
    E.row[at].render = NULL;
    E.row[at].hl = NULL;
    editorUpdateRow(&E.row[at]);

    E.numrows++;
    E.dirty++;
}

void editorFreeRow(erow *row) {
    free(row->render);
    free(row->chars);
    free(row->hl);
}

void editorDelRow(int at) {
    if (at < 0 || at >= E.numrows) return;
    editorFreeRow(&E.row[at]);
    memmove(&E.row[at], &E.row[at + 1], sizeof(erow) * (E.numrows - at - 1)); //delete current line, and rest rows move up one line
    E.numrows--;
    E.dirty++;
}

// insert ordinary characters
void editorRowInsertChar(erow *row, int at, int c) {
    // at is the index we want to insert the character into
    if (at < 0 || at > row->size) at = row ->size;
    row->chars = realloc(row->chars, row->size + 2);
    memmove(&row->chars[at + 1], &row->chars[at], row->size - at + 1);
    row->size++;
    row->chars[at] = c;
    editorUpdateRow(row);
    E.dirty++;
}

void editorInsertNewLine() {
    if (E.cx == 0) {
        editorInsertRow(E.cy, "", 0);
    } else {
        erow *row = &E.row[E.cy];
        editorInsertRow(E.cy + 1, &row->chars[E.cx], row->size - E.cx);
        row = &E.row[E.cy];
        row->size = E.cx;
        row->chars[row->size] = '\0';
        editorUpdateRow(row);
    }
    E.cy++;
    E.cx = 0;
}

void editorRowAppendString(erow* row, char* s, size_t len) {
    row->chars = realloc(row->chars, row->size + len + 1);
    memcpy(&row->chars[row->size], s, len);
    row->size += len;
    row->chars[row->size] = '\0';
    editorUpdateRow(row);
    E.dirty++;
}

void editorRowDelChar(erow *row, int at) {
    if (at < 0 || at >= row->size) return;
    memmove(&row->chars[at], &row->chars[at + 1], row->size - at);
    row->size--;
    editorUpdateRow(row);
    E.dirty++;
}

/*editor operation*/

void editorInsertChar(int c) {
    // append a new line
    if (E.cy == E.numrows) {
        editorInsertRow(E.numrows, "", 0);
    }
    editorRowInsertChar(&E.row[E.cy], E.cx, c);
    E.cx++;
}

void editorDelChar() {
    if (E.cy == E.numrows) return;
    if (E.cx == 0 && E.cy == 0) return;

    erow *row = &E.row[E.cy];
    if (E.cx > 0) {
        editorRowDelChar(row, E.cx - 1);
        E.cx--;
    } else {
        E.cx = E.row[E.cy - 1].size; // move to the end of the previous line
        editorRowAppendString(&E.row[E.cy - 1], row->chars, row->size);
        editorDelRow(E.cy);
        E.cy--;
    }
}

/*file i/o*/

char* editorRowsToString(int *buflen) {
    // add uo the lengths of each row of text
    int totlen = 0;
    int j;
    for (j = 0; j < E.numrows; j++) {
        totlen += E.row[j].size + 1; // adding 1 to each one for the newline character
    }
    *buflen = totlen;

    char *buf = malloc(totlen);
    char *p = buf;
    for (j = 0; j < E.numrows; j++) {
        memcpy(p, E.row[j].chars, E.row[j].size);
        p += E.row[j].size;
        *p = '\n';
        p++;
    }

    return buf;
}

void editorOpen(char *filename) {
    free(E.filename);
    E.filename = strdup(filename);

    editorSelectSyntaxHighlight();

    FILE *fp = fopen(filename, "r");
    if (!fp) {
		int fd = open(filename, O_CREAT, 0644);
		if (fd == -1) {
			die("fopen");
		}
		fp = fdopen(fd, "r");
	}

    char *line = NULL; // a pointer to a text line from file
    size_t linecap = 0; // line capacity(let you know how much memory it allocated)
    ssize_t linelen;
    while ((linelen = getline(&line, &linecap, fp)) != -1) {
        while (linelen > 0 && (line[linelen - 1] == '\n' || line[linelen - 1] == '\r'))
            // \n or \r can be viewed as a end symbol of a text line
            linelen--;
        // update the editor
        editorInsertRow(E.numrows, line, linelen);
    }
    free(line);
    fclose(fp);
    E.dirty = 0;
}

void editorSave() {
    if (E.filename == NULL) {
        E.filename = editorPrompt("Save as: %s (ESC to cancle)", NULL);
        if (E.filename == NULL) {
            editorSetStatusMessage("Save aborted");
            return;
        }
        editorSelectSyntaxHighlight();
    }

    int len;
    char *buf = editorRowsToString(&len);

    int fd = open(E.filename, O_RDWR | O_CREAT, 0644);
    // error handling
    if (fd != -1) {
        if (ftruncate(fd, len) != -1) {
            if (write(fd, buf, len) == len) {
                close(fd);
                free(buf);
                E.dirty = 0;
                editorSetStatusMessage("%d bytes written to disk", len);
                return;
            }
        }
        close(fd);
    }
    free(buf);
    editorSetStatusMessage("Can't sava! I/O error: %s", strerror(errno));
}

/*find*/

void editorFindCallback(char *query, int key) {
    static int last_match = -1; // contain the index of the row that the last match was on
    static int direction = 1; // store the direction of the search, 1 for searching forward and -1 for searching backward

    // restore syntax highlighting after search
    static int saved_hl_line;
    static char *saved_hl = NULL;

    if (saved_hl) {
        memcpy(E.row[saved_hl_line].hl, saved_hl, E.row[saved_hl_line].rsize);
        free(saved_hl);
        saved_hl = NULL;
    }

    if (key == '\r' || key == '\x1b') { //about to leace search mode,reset values
        last_match = -1;
        direction = 1;
        return;
    } else if (key == ARROW_RIGHT || key == ARROW_DOWN) {
        direction = 1;
    } else if (key == ARROW_LEFT || key == ARROW_UP) {
        direction = -1;
    } else { // keypress is not special key, then reset
        last_match = -1;
        direction = 1;
    }

    // never match before
    if (last_match == -1) {
        direction = 1;
    }
    int current = last_match;

    int i;
    for (i = 0; i < E.numrows; i++) {
        current += direction; // update row index according to the search direction
        if (current == -1) current = E.numrows - 1;
        else if (current == E.numrows) current = 0;

        erow *row = &E.row[current];
        char *match = strstr(row->render, query);
        if (match) {
            last_match = current;
            E.cy = current;
            E.cx = editorRowRxToCx(row, match - row->render);
            E.rowoff = E.numrows; // make sure the matched line is visiable

            saved_hl_line = current;
            saved_hl = malloc(row->rsize);
            memcpy(saved_hl, row->hl, row->rsize);
            memset(&row->hl[match - row->render], HL_MATCH, strlen(query));
            break;
        }
    }
}

void editorFind() {
    int saved_cx = E.cx;
    int saved_cy = E.cy;
    int saved_coloff = E.coloff;
    int saved_rowoff = E.rowoff;

    char *query = editorPrompt("Search: %s (EXC/Arrows/Enter)", editorFindCallback);

    if (query) {
        free(query);
    } else {
        E.cx = saved_cx;
        E.cy = saved_cy;
        E.coloff = saved_coloff;
        E.rowoff = saved_rowoff;
    }
}

/*append buffer*/

// replace all our write() calls with code that appends the string to a buffer, and then write() this buffer out at the end
struct abuf {
    char *b;
    int len;
};

#define ABUF_INIT {NULL, 0}

void abAppend(struct abuf *ab, const char * s, int len) {
    char *new = realloc(ab->b, ab->len + len); // allocate memory

    if (new == NULL)
        return;
    memcpy(&new[ab->len], s, len); // copy---plus
    //update
    ab->b = new;
    ab->len += len;
}

void abFree(struct abuf *ab) {
    free(ab->b);
}

/*output*/

// check if the cursor has moved outside of the visible window, and if so, adjust E.rowoff so that the cursor is just inside the visible window.
void editorScroll() {
    E.rx = 0;
    if (E.cy < E.numrows) {
        E.rx = editorRowCxToRx(&E.row[E.cy], E.cx);
    }

    //vertical scrolling
    // if the cursor is above the visiable window
    if (E.cy < E.rowoff) {
        E.rowoff = E.cy;
    }
    // if the cursor is past the bottom of the visiable window
    if (E.cy >= E.rowoff + E.screenrows) { // E.rowoff + E.screenrows equal (the bottom line + 1)
        E.rowoff = E.cy - E.screenrows + 1; // when rowoff == (the bottom + 1), the cursor is visiable
    }

    //horizontal scrolling
    if (E.rx < E.coloff) {
        E.coloff = E.rx;
    }
    if (E.rx >= E.coloff + E.screencols) {
        E.coloff = E.rx - E.screencols + 1;
    }
}

void editorDrawRows(struct abuf *ab) {
    int y;
    // draw a tilde at the beginning of every line
    for (y = 0; y < E.screenrows; y++) {
        // check whether drawing a row that is part of the text buffer or a row that comes after the end of the text buffer
        int filerow = y + E.rowoff;
        if (filerow >= E.numrows) {
            // check E.numrows == 0 means want the welcome message to only display when the user starts with no arguments and not when open a file
            if (E.numrows == 0 && y == E.screenrows / 3) {
                char welcome[80];
                int welcomelen = snprintf(welcome, sizeof(welcome), "Kilo eidtor -- version %s", KILO_VERSION);
                if (welcomelen > E.screencols)
                    welcomelen = E.screencols; //truncate the length of the string in case the terminal is too tiny to fit message
                // center the string
                int padding = (E.screencols - welcomelen) / 2;
                if (padding) {
                    abAppend(ab, "~", 1);
                    padding--;
                }
                while (padding--)
                    abAppend(ab, " ", 1);
                abAppend(ab, welcome, welcomelen);
            } else {
                abAppend(ab, "~", 1);
            }
        } else {
            int len = E.row[filerow].rsize - E.coloff;
            if (len < 0) len = 0; // E.row[filerow].size < E.coloff
            if (len > E.screencols) len = E.screencols;
            char *c = &E.row[filerow].render[E.coloff];
            unsigned char* hl = &E.row[filerow].hl[E.coloff];
            int current_color = -1; // we do not need write an escape sequence for every character, so we use current_color to rocord and decide whether to write by comparing with next_color
            int j;
            for (j = 0; j < len; j++) {
                if (hl[j] == HL_NORMAL) {
                    if (current_color != -1) {
                        abAppend(ab, "\x1b[39m", 5);
                        current_color = -1;
                    }
                    abAppend(ab, &c[j], 1);
                } else {
                    int color = editorSyntaxToColor(hl[j]);
                    if (color != current_color) {
                        current_color = color;
                        char buf[16];
                        int clen = snprintf(buf, sizeof(buf), "\x1b[%dm", color);
                        abAppend(ab, buf, clen);
                    }
                    abAppend(ab, &c[j], 1);
                }
            }
            abAppend(ab, "\x1b[39m", 5);
        }

        abAppend(ab, "\x1b[K", 3); // K command erases part of the current line
        // avoid the case that the last line has not a tilde
        abAppend(ab, "\r\n", 2);
    }
}

void editorDrawStatusBar(struct abuf* ab) {
    abAppend(ab, "\x1b[7m", 4); // The escape sequence <esc>[7m switches to inverted colors
    char status[80], rstatus[80];
    int len = snprintf(status, sizeof(status), "%.20s - %d lines %s", E.filename ? E.filename : "[No Name]", E.numrows, E.dirty ? "(modified)" : "");
    int rlen = snprintf(rstatus, sizeof(rstatus), "%s | %d/%d", E.syntax ? E.syntax->filetype : "no ft", E.cy + 1, E.numrows);
    if (len > E.screencols) len = E.screencols;
    abAppend(ab, status, len);
    while (len < E.screencols) {
        if (E.screencols - len == rlen) {
            abAppend(ab, rstatus, rlen);
            break;
        } else {
            abAppend(ab, " ", 1);
            len++;
        }
    }
    abAppend(ab, "\x1b[m", 3); //  <esc>[m switches back to normal formatting
    abAppend(ab, "\r\n", 2);
}

void editorDrawMessageBar(struct abuf* ab) {
    abAppend(ab, "\x1b[K", 3);
    int msglen = strlen(E.statusmsg);
    if (msglen > E.screencols) msglen = E.screencols;
    if (msglen && time(NULL) - E.statusmsg_time < 5)
        abAppend(ab, E.statusmsg, msglen);
}

void editorRefreshScreen() { 
    editorScroll();

    struct abuf ab = ABUF_INIT;

    abAppend(&ab, "\x1b[?25l", 6); // hide cursor before refreshing
    abAppend(&ab, "\x1b[H", 3); // H command---position the cursor at top-left


    editorDrawRows(&ab);
    editorDrawStatusBar(&ab);
    editorDrawMessageBar(&ab);

    abAppend(&ab, "\x1b[H", 3); // H command---position the cursor at top-left

    //move the cursor
    char buf[32];
    snprintf(buf, sizeof(buf), "\x1b[%d;%dH", (E.cy - E.rowoff) + 1, (E.rx - E.coloff) + 1); // terminal uses 1-indexed values
    abAppend(&ab, buf, strlen(buf));

    abAppend(&ab, "\x1b[?25h", 6); // show cursor after refreshing immediately

    write(STDOUT_FILENO, ab.b, ab.len);
    abFree(&ab);
}

void editorSetStatusMessage(const char* fmt, ...) {
    va_list ap;
    va_start(ap, fmt);
    vsnprintf(E.statusmsg, sizeof(E.statusmsg), fmt, ap);
    va_end(ap);
    E.statusmsg_time = time(NULL);
}

/*input*/

//prompting the user to input a filename when saving a new file and so on
char* editorPrompt(char *prompt, void (*callback)(char *, int)) {
    size_t bufsize = 128;
    char *buf = malloc(bufsize); // store user input

    size_t buflen = 0;
    buf[0] = '\0';

    while (1) {
        editorSetStatusMessage(prompt, buf);
        editorRefreshScreen();

        int c = editorReadKey();

        if (c == DEL_KEY || c == CTRL_KEY('h') || c == BACKSPACE) {
            if (buflen != 0) buf[--buflen] = '\0';
        } else if (c == '\x1b') { // cancle input by press escKey
            editorSetStatusMessage("");
            if (callback) callback(buf, c);
            free(buf);
            return NULL;
        } else if (c == '\r') { // input over by press enterKey
            if (buflen != 0) { // check input is not empty
                editorSetStatusMessage("");
                if (callback) callback(buf, c);
                return buf;
            }
        } else if (!iscntrl(c) && c < 128) { // make sure the input key is not one of the special keys in the editorKey enum
            if (buflen == bufsize - 1) {
                bufsize *= 2;
                buf = realloc(buf, bufsize);
            }
            buf[buflen++] = c;
            buf[buflen] = '\0';
        }

        if (callback) callback(buf, c);
    }
}

void editorMoveCursor(int key) {
    erow *row = (E.cy >= E.numrows) ? NULL : &E.row[E.cy]; 

    switch (key) {
        case ARROW_LEFT :
            if (E.cx != 0) {
                E.cx--;
            } else if (E.cy > 0) { //moving the left at the start of line ---> goto the end of the previous line
                E.cy--;
                E.cx = E.row[E.cy].size;
            }
            break;
        case ARROW_RIGHT :
            if (row && E.cx < row->size) {
                E.cx++;
            } else if (row && E.cx == row->size) { // moving the right at the end of line
                E.cy++;
                E.cx = 0;
            }
            break;
        case ARROW_UP :
            if (E.cy != 0) {
                E.cy--;
            }
            break;
        case ARROW_DOWN :
            if (E.cy < E.numrows) {
                E.cy++;
            }
            break;
        }
    // snap the cursor to end of line
    row = (E.cy >= E.numrows) ? NULL : &E.row[E.cy];
    int rowlen = row ? row->size : 0;
    if (E.cx > rowlen) {
        E.cx = rowlen;
    }
}

void editorProcessKeypress() { //wait for keypress and handle it
    static int quit_times = KILO_QUIT_TIMES;

    int c = editorReadKey();

    switch(c) {
        case '\r' :
            editorInsertNewLine();
            break;
        case CTRL_KEY('q'):
            if (E.dirty && quit_times > 0) {
                editorSetStatusMessage("WARNING!!! File has unsaved changes. ""Press Ctrl-Q %d more times to quit.", quit_times);
                quit_times--;
                return;
            }
            write(STDOUT_FILENO, "\x1b[2J", 4); //write an escape sequence out to the terminal to clear
			write(STDOUT_FILENO, "\x1b[H", 3); // locate cursor to left top corner 
			// exit(0);
			kilo_end = 1;
			break;
        case CTRL_KEY('s'):
            editorSave();
            break;
        
        //currsor move to the left or right edge        
        case HOME_KEY :
            E.cx = 0;
            break;
        case END_KEY :
            if (E.cy < E.numrows)
                E.cx = E.row[E.cy].size;
            break;

        case CTRL_KEY('f') :
            editorFind();
            break;

        case BACKSPACE :
        case CTRL_KEY('h') :
        case DEL_KEY :
            if (c == DEL_KEY) editorMoveCursor(ARROW_RIGHT); // del key delete the char in the right of currsor
            editorDelChar(); // other key delete the char in the left of currsor
            break;
        // cursor move to the top or bottom of the screen
        case PAGE_UP :
        case PAGE_DOWN :
        {
            if (c == PAGE_UP) {
                E.cy = E.rowoff;
            } else if (c == PAGE_DOWN) {
                E.cy = E.rowoff + E.screenrows - 1;
                if (E.cy > E.numrows) E.cy = E.numrows;
            }

            int times = E.screenrows;
            while (times--) {
                editorMoveCursor(c == PAGE_UP ? ARROW_UP : ARROW_DOWN);
            }
            break;
        }

        case ARROW_UP :
        case ARROW_DOWN :
        case ARROW_LEFT :
        case ARROW_RIGHT :
            editorMoveCursor(c);
            break;
        
        case CTRL_KEY('l') :
        case '\x1b' :
            break;

        default :
            editorInsertChar(c);
            break;
    }

    quit_times = KILO_QUIT_TIMES;
}

/*init*/

void initEditor() {
    // C language use 0-indexded values
    E.cx = 0;
    E.cy = 0;
    E.rx = 0;
    E.rowoff = 0; //means scroll to the top of the file by default
    E.coloff = 0;
    E.numrows = 0;
    E.row = NULL;
    E.dirty = 0;
    E.filename = NULL;
    E.statusmsg[0] = '\0';
    E.statusmsg_time = 0;
    E.syntax = NULL;

    if (getWindowSize(&E.screenrows, &E.screencols) == -1)
        die("getWindowSize");
    E.screenrows -= 2; //make room for a one-line status bar and one-line statusmsg
}

int _kilo(char *argv[]) {
	/* check the number of cmd-line parameters is correct */
	int argc = 0;
	for (char **ptr = argv; *ptr != NULL; ptr++)
		argc++;

    //enter row mode
    enableRawMode();
    // initialize the editor
    initEditor();
    if (argc >= 2) {
        editorOpen(argv[1]);
    }

    editorSetStatusMessage("HELP: Ctrl-S = Save | Ctrl-Q = quit | Ctrl-F = find");

    while (kilo_end != 1) {
		kilo_end = 0;
        editorRefreshScreen();
        editorProcessKeypress();
    }
	disableRawMode();
    return 0;
}
